package com.atguigu.tingshu.album.service.impl;

import com.atguigu.tingshu.album.service.BloomFilterService;
import com.atguigu.tingshu.album.service.TestService;
import com.atguigu.tingshu.common.util.DelayMsgService;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Service;

import java.sql.Time;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @author: atguigu
 * @create: 2023-10-07 08:54
 */
@Slf4j
@Service
public class TestServiceImpl implements TestService {

    @Autowired
    private StringRedisTemplate redisTemplate;


    /**
     * 使用自定义分布式锁
     */
   /* @Override
    public void testLock() {
        //一、先获取分布式锁
        //版本一：利用Redis提供setnx+expire命令实现 问题：加锁跟设置过期时间不具备原子性 导致死锁出现
        //版本二：利用Redis提供set k v ex nx 命令实现 问题：当A线程获取锁后业务超时，A锁自动释放，B线程获取锁成功ing,A恢复后将B锁释放。线程间互相释放锁
        //版本三：利用存入锁时候加入线程标识信息，删除锁前判断锁是否为当前线程 问题：释放锁判断跟删除不具备原子性（A线程判断通过后，锁刚好自动失效，B线程获取锁成功。A将B的锁删除）
        //版本四：利用lua脚本具备原子性特点 问题：

        String lockKey = "lock";
        String lockVal = UUID.randomUUID().toString(); //当前线程锁标识
        Boolean flag = redisTemplate.opsForValue().setIfAbsent(lockKey, lockVal, 5, TimeUnit.SECONDS);
        if (flag) {
            //二、获取锁成功执行业务
            try {
                //1.从Redis缓存中获取key="num"的值  保证redis中存在"num"(手动提前在redis中创建key)
                String value = redisTemplate.opsForValue().get("num");
                if (StringUtils.isBlank(value)) {
                    return;
                }
                //2.对获取到值进行+1操作
                int num = Integer.parseInt(value);
                redisTemplate.opsForValue().set("num", String.valueOf(++num));

            } catch (NumberFormatException e) {
                e.printStackTrace();
            } finally {
                //三、释放
               *//* if (redisTemplate.opsForValue().get(lockKey).equals(lockVal)) {
                    //当前线程内存储线程标识跟Redis存放一样-说明锁是当前线程的
                    redisTemplate.delete(lockKey);
                }*//*
                String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" +
                        "then\n" +
                        "    return redis.call(\"del\",KEYS[1])\n" +
                        "else\n" +
                        "    return 0\n" +
                        "end";
                DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
                redisScript.setScriptText(script);
                redisScript.setResultType(Long.class);
                redisTemplate.execute(redisScript, Arrays.asList(lockKey), lockVal);
            }
        } else {
            //四、获取锁失败，结束or自旋 看业务是需要线程必须执行业务
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            this.testLock();
        }

    }*/


    @Autowired
    private RedissonClient redissonClient;


    /**
     *
     */
    @Override
    public void testLock() {
        //一、获取锁
        //①获取锁对象 传入字符串代表锁名称
        RLock lock = redissonClient.getLock("myLock");
        //②获取分布式锁
        lock.lock();

        //二、获取锁成功执行业务逻辑
        try {
            log.info("获取锁成功：线程：{}", Thread.currentThread().getName());
            //1.从Redis缓存中获取key="num"的值  保证redis中存在"num"(手动提前在redis中创建key)
            String value = redisTemplate.opsForValue().get("num");
            if (StringUtils.isBlank(value)) {
                return;
            }
            //2.对获取到值进行+1操作
            int num = Integer.parseInt(value);
            redisTemplate.opsForValue().set("num", String.valueOf(++num));
        } finally {
            //三、释放锁
            lock.unlock();
        }
    }

    /**
     * 写数据方法
     *
     * @param id
     */
    @Override
    public void write(Long id) {
        //1.创建读写锁对象 锁名称前缀:数据标识
        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("myLock:" + id);
        //2.获取写锁成功 执行写操作  写锁持有期间，不允许并发读，并发写
        RLock lock = readWriteLock.writeLock();
        lock.lock(5, TimeUnit.SECONDS);
        try {
            log.info(Thread.currentThread().getName() + "执行写入操作");
        } finally {
            //3.释放写锁
            //lock.unlock();
        }

    }

    /**
     * 读数据方法
     *
     * @param id
     * @return
     */
    @Override
    public Long read(Long id) {
        //1.创建读写锁对象 锁名称前缀:数据标识
        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("myLock:" + id);
        RLock lock = readWriteLock.readLock();

        //2.获取读锁成功 执行读操作  读锁持有期间，不允许并发发写，允许并发读
        try {
            lock.lock(5, TimeUnit.SECONDS);
            log.info(Thread.currentThread().getName() + "执行读数据操作");
            return id;
        } finally {
            //3.释放读锁
            //lock.unlock();
        }
    }

    @Autowired
    private BloomFilterService bloomFilterService;

    /**
     * 布隆过滤器扩容
     *
     * @return
     */
    @Override
    public Map<String, Object> bloomfilterResize() {
        RBloomFilter bloomFilter = bloomFilterService.resize();
        if (bloomFilter != null) {
            log.info("[布隆过滤器扩容]，扩容成功：元素个数：{}，容量：{}，误判率：{}，hash个数：{}", bloomFilter.count(), bloomFilter.getExpectedInsertions(), bloomFilter.getFalseProbability(), bloomFilter.getHashIterations());
            Map<String, Object> map = new HashMap<>();
            map.put("count", bloomFilter.count());
            map.put("size", bloomFilter.getExpectedInsertions());
            map.put("hashSize", bloomFilter.getHashIterations());
            return map;
        }
        return null;
    }


    @Autowired
    private DelayMsgService delayMsgService;


    @Override
    public void sendDelayMsg(String msg, Long ttl) {
        String queueName = "myDelay";
        delayMsgService.sendDelayMsg(queueName, msg, ttl, TimeUnit.SECONDS);
    }


    /**
     * 将该方法结果放入Cache中（默认放入JVM） 缓存key:注解中信息 方法结果
     *
     * @param id
     * @return
     */
    @Cacheable(value = "a", key = "#id")
    public String a(String id) {
        return "ab";
    }


    /**
     * 当前类构造方法执行完毕后自动触发一次
     * 开启一个线程负责监听阻塞队列中延迟消息
     */
    @PostConstruct
    public void listenDealyMsg() {
        log.info("[专辑服务]开启线程监听延迟性消息");
        //1.获取阻塞队列
        String queueName = "myDelay";
        RBlockingQueue<String> myDelay = redissonClient.getBlockingQueue(queueName);
        new Thread(() -> {
            while (true) {
                String data = null;
                try {
                    data = myDelay.poll(1, TimeUnit.SECONDS);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                if (StringUtils.isNotBlank(data)) {
                    log.info("监听到：{}的延迟消息：{}", queueName, data);
                } else {
                    //log.info("未拉取到数据，则进行下一次拉取");
                }
            }
        }).start();
    }
}
